package cn.agiledata.bank.transaction.service;

import java.math.BigDecimal;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import cn.agiledata.bank.audit.exception.AuditException;
import cn.agiledata.bank.common.dao.AccountTemplateInter;
import cn.agiledata.bank.common.dao.BankDao;
import cn.agiledata.bank.common.util.Constant;
import cn.agiledata.bank.common.util.ForeDict;
import cn.agiledata.bank.common.util.PageBean;
import cn.agiledata.bank.common.util.UniqueIdentityUtil;
import cn.agiledata.bank.common.util.WeatherUtil;
import cn.agiledata.bank.hibernateBean.Account;
import cn.agiledata.bank.hibernateBean.Bank;
import cn.agiledata.bank.hibernateBean.CityInfo;
import cn.agiledata.bank.hibernateBean.EsPrePayTrans;
import cn.agiledata.bank.hibernateBean.Groups;
import cn.agiledata.bank.hibernateBean.Operator;
import cn.agiledata.bank.hibernateBean.PeerBankAccount;
import cn.agiledata.bank.hibernateBean.PeerInnerAccount;
import cn.agiledata.bank.hibernateBean.SubAccount;
import cn.agiledata.bank.hibernateBean.Transaction;
import cn.agiledata.bank.myspace.dao.PeerBankAccountDao;
import cn.agiledata.bank.myspace.service.IMySpaceService;
import cn.agiledata.bank.myspace.service.PeerAccountService;
import cn.agiledata.bank.transaction.audit.service.EsPrePayTransService;
import cn.agiledata.bank.transaction.dao.ActionTypeDao;
import cn.agiledata.bank.transaction.dao.ReturnTransQueryPageList;
import cn.agiledata.bank.transaction.dao.TransactionDao;
import cn.agiledata.bank.transaction.exception.TransactionException;
import cn.agiledata.bank.transaction.logic.BatchImportFile;
import cn.agiledata.bank.transaction.logic.BatchImportFileManager;
import cn.agiledata.bank.transaction.logic.BatchImportTrans;
import cn.agiledata.bank.transaction.logic.BatchPeerAccount;
import cn.agiledata.bank.transaction.logic.PeerAccountAmount;
import cn.agiledata.bank.transaction.logic.ReturnQueryBean;

import com.vnv.infrastructure.logic.IPBAppFactory;

public class TransactionServiceImpl implements TransactionService {
    private TransactionDao transactionDao;

    private IMySpaceService mySpaceService;

    private FeeRatioInterf feeRatioService;

    private PeerBankAccountDao peerBankAccountDao;

    private BankDao bankDao;

    private ActionTypeDao actionTypeDao;

    private EsPrePayTransService esPrePayTransService;

    private AccountTemplateInter accountTemplate;

    private PeerAccountService peerAccountService;

    private BatchImportFileManager batchImportFileManager;

    public void setTransactionDao(TransactionDao transactionDao) {
        this.transactionDao = transactionDao;
    }

    public void setMySpaceService(IMySpaceService mySpaceService) {
        this.mySpaceService = mySpaceService;
    }

    public void setFeeRatioService(FeeRatioInterf feeRatioService) {
        this.feeRatioService = feeRatioService;
    }

    public void setPeerBankAccountDao(PeerBankAccountDao peerBankAccountDao) {
        this.peerBankAccountDao = peerBankAccountDao;
    }

    public void setBankDao(BankDao bankDao) {
        this.bankDao = bankDao;
    }

    public void setActionTypeDao(ActionTypeDao actionTypeDao) {
        this.actionTypeDao = actionTypeDao;
    }

    public void setEsPrePayTransService(
            EsPrePayTransService esPrePayTransService) {
        this.esPrePayTransService = esPrePayTransService;
    }

    public void setAccountTemplate(AccountTemplateInter accountTemplate) {
        this.accountTemplate = accountTemplate;
    }

    public void setPeerAccountService(PeerAccountService peerAccountService) {
        this.peerAccountService = peerAccountService;
    }

    public void setBatchImportFileManager(
            BatchImportFileManager batchImportFileManager) {
        this.batchImportFileManager = batchImportFileManager;
    }

    public BigDecimal getDebitFee(SubAccount debitSubAccount,
            String actionType, BigDecimal amount) {
        double fee = this.feeRatioService.getDebitFee(debitSubAccount,
                actionType, new Double(amount.doubleValue()), null);
        return new BigDecimal(fee);
    }

    public BigDecimal getCreditFee(SubAccount creditSubAccount,
            String actionType, BigDecimal amount) {
        double fee = this.feeRatioService.getCreditFee(null, creditSubAccount
                .getAccount(), actionType, new Double(amount.doubleValue()));
        return new BigDecimal(fee);
    }

    public String getFeeModeDescription(Account account, String actionType) {
        Groups group = this.mySpaceService.getGroupByAccount(account,
                Constant.GROUP_TYPE_A);
        return this.feeRatioService.getFeeRatio(group, actionType);
    }

    public void validateTransactable(Account debit, Account credit,
            String creditName, String actionType) throws TransactionException {
        if (Constant.ACCOUNT_TYPE_CORPORATION.equals(debit.getAccountType())
                && Constant.B2C_NOTAVAIABLE.equals(debit.getAccountCompany()
                        .getB2cable())) {
            if (Constant.ACTION_TYPE_C.equals(actionType)) {
                if (Constant.ACCOUNT_TYPE_BUYER.equals(credit.getAccountType())) {
                    throw TransactionException.B2C_NOTAVAIABLE;
                }
            } else if (Constant.ACTION_TYPE_G.equals(actionType)) {
                if (creditName != null && creditName.length() < 5) {
                    throw TransactionException.B2C_NOTAVAIABLE;
                }
            }
        }
    }

    public void validateBatchTransactable(Account debit,
            BatchPeerAccount batchPeerAccount) throws TransactionException {
        if (Constant.ACCOUNT_TYPE_CORPORATION.equals(debit.getAccountType())
                && Constant.B2C_NOTAVAIABLE.equals(debit.getAccountCompany()
                        .getB2cable())) {
            PeerAccountAmount[] paas = batchPeerAccount.getValues();
            if (batchPeerAccount.isInnerBatchPeerAccount()) {
                for (int i = 0; i < paas.length; i++) {
                    PeerInnerAccount pia = this.peerAccountService
                            .getPeerInnerAccount(paas[i].getId());
                    Account credit = pia.getPeerAccount();
                    if (Constant.ACCOUNT_TYPE_BUYER.equals(credit
                            .getAccountType())) {
                        throw TransactionException.B2C_NOTAVAIABLE;
                    }
                }
            } else if (batchPeerAccount.isBankBatchPeerAccount()) {
                for (int i = 0; i < paas.length; i++) {
                    PeerBankAccount pba = this.peerAccountService
                            .getPeerBankAccount(paas[i].getId());
                    String creditName = pba.getPeerName();
                    if (creditName != null && creditName.length() < 5) {
                        throw TransactionException.B2C_NOTAVAIABLE;
                    }
                }
            }
        }
    }

    public Transaction getTransactionByTransId(String transId) {
        if (transId == null || transId.length() == 0) {
            return null;
        }
        return this.transactionDao.getTransactionByTransId(transId);
    }

    public boolean isAffirmedTransaction(Transaction trans) {
        String transStatus = trans.getTransStatus();
        return Constant.TRANS_STATUS_WAITFORBUYER.equals(transStatus)
                || Constant.TRANS_STATUS_REFUNDSUCC.equals(transStatus)
                || Constant.TRANS_STATUS_TRANSACTIONSUCCESS.equals(transStatus)
                || Constant.TRANS_STATUS_TRANSACTIONFAIL.equals(transStatus)
                || Constant.TRANS_STATUS_TRANSACTIONCLOSE.equals(transStatus);
    }

    public BatchPeerAccount createBatchPeerAccountByBatchImportFile(
            BatchImportFile file) throws TransactionException {
        BatchPeerAccount bpa = BatchPeerAccount.getBankBatchPeerAccount();
        String accountId = file.getAccountId();
        Account account = this.accountTemplate.getAccountByAccountId(accountId);
        if (account == null) {
            throw TransactionException.getUnexistedAccountException(accountId);
        }
        Date date = new Date();
        for (int i = 0; i < file.getCount(); i++) {
            BatchImportTrans trans = file.getTrans(i);
            // 查找或生成批量导入交易对应的交易对方银行账户
            String accountNo = trans.getAccountNo();
            PeerBankAccount pba = this.peerBankAccountDao
                    .getPeerBankAccount(Constant.PEER_ACCOUNT_SOURCE_IMPORTED,
                            accountId, accountNo);
            if (pba == null) { // 如果没有该交易对方银行账户，则新建
                pba = new PeerBankAccount();
                pba.setSource(Constant.PEER_ACCOUNT_SOURCE_IMPORTED);
                pba.setAccount(account);
                pba.setCardNo(accountNo);
                pba.setCreateTime(date);
                pba.setCreateUser(account.getId().toString());
                pba.setUpdateUser(pba.getCreateUser());
            }
            // 不论此前交易对方银行账户中的银行信息是否正确，一律用新的银行信息替换
            pba.setPeerName(trans.getAccountName());
            Bank bank = this.bankDao.getBankByCode(trans.getBankCode());
            if (bank == null) {
                throw TransactionException.DRAW_UNSUPPORTED_BANK;
            }
            pba.setBank(bank);
            pba.setBankName(trans.getBankName());
            pba.setUpdateTime(date);
            // 保存以生成主键id
            this.peerBankAccountDao.saveOrUpdatePeerBankAccount(pba);

            long peerAccountId = pba.getId().longValue();
            BigDecimal amount = trans.getAmount();
            SubAccount debit = account.getDefaultSubAccount();
            BigDecimal fee = getDebitFee(debit, Constant.ACTION_TYPE_G, trans
                    .getAmount());
            bpa.put(peerAccountId, amount, fee);
            // 设置订单编号
            bpa.setOrderId(peerAccountId, trans.getOrderId());
        }
        return bpa;
    }

    
    /**
     * 创建批量提现到他人交易
     * 
     * @throws TransactionException
     *             如果创建过程中出现错误
     */
    private Transaction createBatchDrawToOtherTransaction(Operator operator,
            SubAccount debit, PeerAccountAmount peerAccountAmount,
            String batchNo, String purpose, String remark)
            throws TransactionException {
        String orderId = peerAccountAmount.getOrderId();
        if (orderId != null && orderId.length() > 0) {
            EsPrePayTrans eppt = this.esPrePayTransService
                    .getEsPrePayTransByDebitOrderId(debit, orderId);
            if (eppt != null) {
                Account debitAccount = debit.getAccount();
                throw TransactionException.getExistedOrderIdException(
                        debitAccount, orderId);
            }
        }
        Transaction trans = new Transaction();
        // 交易编号
        trans.setTransId(UniqueIdentityUtil.getTransactionSequence());
        // 订单编号
        trans.setOrderId(orderId);
        // 交易类型：账户付款给对方银行
        String actionType = Constant.ACTION_TYPE_G;
        trans.setActionType(this.actionTypeDao.getActionType(Long
                .parseLong(actionType)));
        // 付款方
        trans.setDebit(debit);
        // 收款方：内部提现账户
        SubAccount credit = this.accountTemplate.getRemitAccount();
        trans.setCredit(credit);
        // 金额
        BigDecimal amount = peerAccountAmount.getAmount();
        trans.setAmount(new Double(amount.doubleValue()));
        // 付款方手续费
        BigDecimal debitFee = peerAccountAmount.getFee();
        trans.setDebitFee(new Double(debitFee.doubleValue()));
        // 收款方手续费
        BigDecimal creditFee = getCreditFee(credit, actionType, amount);
        trans.setCreditFee(new Double(creditFee.doubleValue()));
        // 批次号
        trans.setBatchNo(batchNo);
        // 非担保交易
        trans.setTradeType(Constant.TRADE_TYPE_NOASSURE);
        // 非担保交易的交易状态：待确认
        trans.setTransStatus(Constant.TRANS_STATUS_INIT);
        // 非担保交易的内部状态：不进行实际转账操作
        trans.setInsideStatus(Constant.INSIDE_STATUS_NO);
        // 封装插入日期和修改日期
        WeatherUtil.fillCreateAndUpdateInfo(trans, debit.getAccount().getId()
                .toString());
        // 交易开始时间
        trans.setBeginDate(new Date());
        // 操作员
        if (operator != null) {
            trans.setOperatorSeq(operator.getId().toString());
        }
        // 资金用途
        trans.setPurpose(purpose);
        // 备注
        trans.setRemark(remark);
        // 记账模型
        trans.setOperate(Constant.MODEL_TYPE_GAINMONEY);
        // 银行账户信息
        PeerBankAccount peerBankAccount = this.peerAccountService
                .getPeerBankAccount(peerAccountAmount.getId());
        trans.setCardNo(peerBankAccount.getCardNo());
        trans.setBankSeq(peerBankAccount.getBank().getId());
        trans.setBankName(peerBankAccount.getBankName());
        // 保存开户行省份和城市的cityId而不是id，和单笔账户直付产生的交易保持一致
        CityInfo province = peerBankAccount.getProvince();
        if (province != null) {
            trans.setBankProvSeq(new Long(province.getCityId()));
        }
        CityInfo city = peerBankAccount.getCity();
        if (city != null) {
            trans.setBankCitySeq(new Long(city.getCityId()));
        }
        trans.setPersonName(peerBankAccount.getPeerName());
        trans.setPersonMail(peerBankAccount.getPeerEmail());
        trans.setPersonMobile(peerBankAccount.getPeerMobile());
        trans.setPersonPhone(peerBankAccount.getPeerPhone());
        return trans;
    }

    /**
     * 创建批量账户间支付交易
     * 
     * @throws TransactionException
     *             如果创建过程中出错
     */
    private Transaction createBatchInnerPayTransaction(Operator operator,
            SubAccount debit, PeerAccountAmount peerAccountAmount,
            String batchNo, String purpose, String remark)
            throws TransactionException {
        String orderId = peerAccountAmount.getOrderId();
        if (orderId != null && orderId.length() > 0) {
            EsPrePayTrans eppt = this.esPrePayTransService
                    .getEsPrePayTransByDebitOrderId(debit, orderId);
            if (eppt != null) {
                Account debitAccount = debit.getAccount();
                throw TransactionException.getExistedOrderIdException(
                        debitAccount, orderId);
            }
        }
        Transaction trans = new Transaction();
        // 交易编号
        trans.setTransId(UniqueIdentityUtil.getTransactionSequence());
        // 订单编号
        trans.setOrderId(orderId);
        // 交易类型：账户间交易
        String actionType = Constant.ACTION_TYPE_C;
        trans.setActionType(this.actionTypeDao.getActionType(Long
                .parseLong(actionType)));
        // 付款方
        trans.setDebit(debit);
        // 收款方
        PeerInnerAccount peerInnerAccount = this.peerAccountService
                .getPeerInnerAccount(peerAccountAmount.getId());
        SubAccount credit = peerInnerAccount.getPeerAccount()
                .getDefaultSubAccount();
        trans.setCredit(credit);
        // 金额
        BigDecimal amount = peerAccountAmount.getAmount();
        trans.setAmount(new Double(amount.doubleValue()));
        // 付款方手续费
        BigDecimal debitFee = peerAccountAmount.getFee();
        trans.setDebitFee(new Double(debitFee.doubleValue()));
        // 收款方手续费
        BigDecimal creditFee = getCreditFee(credit, actionType, amount);
        trans.setCreditFee(new Double(creditFee.doubleValue()));
        // 批次号
        trans.setBatchNo(batchNo);
        // 非担保交易
        trans.setTradeType(Constant.TRADE_TYPE_NOASSURE);
        // 非担保交易的交易状态：交易成功
        trans.setTransStatus(Constant.TRANS_STATUS_INIT);
        // 非担保交易的内部状态：不进行实际转账操作
        trans.setInsideStatus(Constant.INSIDE_STATUS_NO);
        // 封装插入日期和修改日期
        WeatherUtil.fillCreateAndUpdateInfo(trans, debit.getAccount().getId()
                .toString());
        // 交易开始时间
        trans.setBeginDate(new Date());
        // 操作员
        if (operator != null) {
            trans.setOperatorSeq(operator.getId().toString());
        }
        // 资金用途
        trans.setPurpose(purpose);
        // 备注
        trans.setRemark(remark);
        // 记账模型
        trans.setOperate(Constant.MODEL_TYPE_PAYMONEY);
        return trans;
    }

    public String saveBatchTransactions(BatchPeerAccount batchPeerAccount,
            SubAccount debit, String purpose, String remark, Operator operator)
            throws TransactionException, AuditException {
        PeerAccountAmount[] paas = batchPeerAccount.getValues();
        String batchNo = UniqueIdentityUtil.getTransactionBatchNoSequence(); // 生成批次号
        if (batchPeerAccount.isInnerBatchPeerAccount()) {
            for (int i = 0; i < paas.length; i++) {
                Transaction trans = createBatchInnerPayTransaction(operator,
                        debit, paas[i], batchNo, purpose, remark);
                Account credit = trans.getCredit().getAccount();
                String actionType = trans.getActionType().getId().toString();
                validateTransactable(debit.getAccount(), credit, credit
                        .getName(), actionType);
                this.esPrePayTransService.createEsPrePayTrans(trans, debit
                        .getAccount(), operator);
            }
        } else {
            for (int i = 0; i < paas.length; i++) {
                Transaction trans = createBatchDrawToOtherTransaction(operator,
                        debit, paas[i], batchNo, purpose, remark);
                Account credit = trans.getCredit().getAccount();
                String actionType = trans.getActionType().getId().toString();
                validateTransactable(debit.getAccount(), credit, trans
                        .getPersonName(), actionType);
                this.esPrePayTransService.createEsPrePayTrans(trans, debit
                        .getAccount(), operator);
            }
        }
        return batchNo;
    }
    
    public String saveBatchInpuor(List inpuorCardList,
            SubAccount debit, String purpose, String remark, Operator operator)
            throws TransactionException, AuditException {
    	String batchNo = UniqueIdentityUtil.getTransactionBatchNoSequence(); // 生成批次号
    	for (int i = 0; i < inpuorCardList.size(); i++) {
    		Transaction trans = null;
    		Account credit = trans.getCredit().getAccount();
            String actionType = trans.getActionType().getId().toString();
            validateTransactable(debit.getAccount(), credit, credit
                    .getName(), actionType);
            this.esPrePayTransService.createEsPrePayTrans(trans, debit
                    .getAccount(), operator);
    	}
    	return batchNo;
    }

    public void importAllTransFromBatchImportFiles() {
        Operator operator = null;
        Logger logger = Logger.getLogger(this.getClass());
        try {
            BatchImportFile[] files = this.batchImportFileManager
                    .loadAllBatchImportFiles();
            for (int i = 0; i < files.length; i++) {
                try {
                    BatchPeerAccount bpa = createBatchPeerAccountByBatchImportFile(files[i]);
                    String accountId = files[i].getAccountId();
                    Account account = this.accountTemplate
                            .getAccountByAccountId(accountId);
                    if (account == null) {
                        throw TransactionException.getUnexistedAccountException(accountId);
                    }
                    SubAccount debit = account.getDefaultSubAccount();
                    String purpose = files[i].getPurpose();
                    String remark = files[i].getRemark();
                    saveBatchTransactions(bpa, debit, purpose, remark, operator);
                    Date date = files[i].getCreateTime();
                    this.batchImportFileManager.backupBatchImportFile(
                            accountId, date);
                } catch (TransactionException e) {
                    logger.error("A TransactionException occurred: "
                            + e.getMessage() + ", next continue ...");
                } catch (AuditException e) {
                    logger.error("A AuditException occurred: " + e.getMessage()
                            + ", next continue ...");
                }
            }
        } catch (TransactionException e) {
            logger.error("A TransactionException occurred: " + e.getMessage()
                    + ", the import process stop.");
        }
    }

	public int createReturnApply(Long[] ids, Map amounts, Account account,Operator operator)
			throws TransactionException, AuditException, Exception {
		// 创建退款交易
		
		// 查询相关交易
		ReturnQueryBean pageBean = new ReturnQueryBean();
    	pageBean.setStartPage("1");
    	pageBean.setAccount(account);
		ReturnTransQueryPageList rtqPage = new ReturnTransQueryPageList(ids,pageBean);
    	PageBean pb = rtqPage.getAccountListPageBean();
    	
    	List listTrans = pb.getData();
    	
    	Double dbTotalAmount = new Double(0);
    	
    	// 检查是否所有交易均可以退款
    	if(listTrans.size() != ids.length) {
    		// 重复提交异常
    		throw TransactionException.REPEATEDLY_SUBMIT;
    	}
    	
    	// 检查退款交易
    	for (Iterator iter = listTrans.iterator(); iter.hasNext();) {
			Transaction trans = (Transaction) iter.next();
			// 检查退款额
			Double dbReturnAmount = (Double)amounts.get(trans.getId());
			if (dbReturnAmount.compareTo(trans.getAmount()) > 0
					|| dbReturnAmount.doubleValue() <= 0) {
				throw TransactionException.RETURN_AMOUNT_ERR;
			}
			
			dbTotalAmount = new Double(dbTotalAmount.doubleValue()
					+ dbReturnAmount.doubleValue());
		}
    	
    	// 检查余额是否足够
    	// 取得默认子账户
    	SubAccount defaultSubAccount = account.getDefaultSubAccount();
    	AccountTemplateInter ati = (AccountTemplateInter) IPBAppFactory
				.getObj(ForeDict.ACCOUNT_TEMPLATE);
    	if (ati.compareAccountMoney(defaultSubAccount.getId().toString(),
				dbTotalAmount) != 0) {
    		// 余额不足
			throw TransactionException.LACK_OF_BALANCE;
		}
    	
    	AccountTemplateInter at = (AccountTemplateInter) IPBAppFactory
				.getObj(ForeDict.ACCOUNT_TEMPLATE);
    	
    	SubAccount returnAccount = at.getReturnAccount();
    	
    	EsPrePayTransService preService = (EsPrePayTransService) IPBAppFactory
				.getObj(ForeDict.SERVICE_ES_PRE_PAY_TRANS);
    	
    	// 检查完毕，创建退款交易
    	for (Iterator iter = listTrans.iterator(); iter.hasNext();) {
			Transaction trans = (Transaction) iter.next();
			
			EsPrePayTrans preReturnTrans = new EsPrePayTrans();
			preReturnTrans.setDebit(defaultSubAccount);
			// 退款内部账户
			preReturnTrans.setCredit(returnAccount);
			// 退款银行
			preReturnTrans.setBankSeq(trans.getBankSeq());
			// 交易类型为退款
			preReturnTrans.setActionType(this.actionTypeDao.getActionType(Long
					.parseLong(Constant.ACTION_TYPE_R)));
			// 交易状态为成功
			preReturnTrans.setTransStatus(Constant.ES_TRANS_STATUS_UNPAY);
			// 生成交易编号
			preReturnTrans.setTransId(UniqueIdentityUtil.getTransactionSequence());
			// 相关交易ID
			preReturnTrans.setRelating(trans.getId()); 
			// 非担保交易
			preReturnTrans.setTradeType(Constant.TRADE_TYPE_NOASSURE);
	        // 退款金额
			preReturnTrans.setAmount((Double)amounts.get(trans.getId()));
			// 交易创建日期
			preReturnTrans.setBeginDate(new Date());
			// 相关交易编号
			String strRemark = "关联交易编号：" + trans.getTransId(); 
			if(trans.getOrderId() != null && trans.getOrderId().trim().length() > 0) {
				strRemark += "　关联订单编号：" + trans.getOrderId();
			}
			preReturnTrans.setRemark(strRemark);
	        // 设置记账模型
	        // 封装插入日期和修改日期
	        WeatherUtil.fillCreateAndUpdateInfo(preReturnTrans, defaultSubAccount.getAccount()
	                .getId().toString());
	        
	        // 创建待退款审核交易
	        preService.createPreReturnTrans(preReturnTrans, account, operator);
		}
    	
		return listTrans.size();
	}
	
	/**
     * 验证退款申请数据是否合法(退款接口调用)
     * @param account
     * @param returnMoneyList
     * @return
     */
    public List getValReturnMoney(Account account, List returnMoneyList) {
    	return transactionDao.getValReturnMoney(account, returnMoneyList);
    }
}